-
-
Notifications
You must be signed in to change notification settings - Fork 723
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add webhook triggered on Order Cycle Open #9687
Add webhook triggered on Order Cycle Open #9687
Conversation
591ae53
to
ccf3d5f
Compare
This comment was marked as outdated.
This comment was marked as outdated.
ccf3d5f
to
ae1ecc3
Compare
5444c1a
to
33dd37c
Compare
65292a4
to
b1e4c2c
Compare
This comment was marked as outdated.
This comment was marked as outdated.
Yes, that's fine for now. It's a pre-existing problem. And I hope that it can be improved when removing AngularJS. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi David!
This is a good start! I left a few comments as there are a couple of code/DB design problems imo.
Webhooks usually are named, to have webhooks for multiple events in the system we need named webhooks. For this first PR I'd recommend we have on the webhooks table a "webhook_type" column that we make "order_cycle_opened". On the user page this is a select box with only one option for now: so the user can add a webhook endpoint for a particular event, in this case "Order Cycle Opened".
As mentioned below on not overloading the OC table, I'd create webhook_log table that the Job would fill when the webhook is called, including user_id, oc_id (this can be a generic column like "event_target_object_id" so that other events can put other data in there), etc. This is better than having webhook log data scattered around the data model.
Good luck with this. This is a good start to a great feature!
app/jobs/order_cycle_opened_job.rb
Outdated
def perform | ||
ActiveRecord::Base.transaction do | ||
recently_opened_order_cycles.find_each do |order_cycle| | ||
SubscriptionPlacementJob.perform_later(order_cycle.id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dont understand the need to mix subscription placement logic with the web hook logic. I think this can be problematic. Can we separate concerns and just keep webhook notification on this job?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that it would be good to have one place where we define the logic selecting and processing order cycles as "opened".
It does seem strange to be creating so many jobs, yes, but it helps "separate concerns" in other ways I guess. I thought it would be more robust this way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand but I think this field name is very misleading. What is a notification? I'd use the same terminology everywhere so that code readers will know what this means. It's a webhook. It could be called opened_webhook_sent_at.
I still think it would be much better and very easy to implement this outside the OC table (there's already lots of complexity in OCs).
A table where webhooks calls are registered is important imho. This would be one of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I agree, but this would be better considered as part of #10457. So for now, I plan to rename this column to match specifically what it refers to.
|
||
class AddOpenedNotificationAtToOrderCycle < ActiveRecord::Migration[6.1] | ||
def change | ||
add_column :order_cycles, :opened_notification_at, :timestamp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this field on the OC meant to mark that a webhook was called? I'd recommend the name of the column to mention that.
I think this should not be on this OC table but rather on a specific webhook calls table/log. This way we are adding complexity to the critical/core OC table without a real need.
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm coming back to this I have a different answer now. We're not intending to log when/if a webhook was called.
This is a flag to ensure the order cycle's "open" state is triggered once, and only once. The application code then schedules any webhooks needing to be delivered. I think I'll leave this as-is for now.
Thank you Luis, this is really helpful feedback! I've responded to some comments and will take this feedback on when I pick the task back up next year. |
b1e4c2c
to
02520ac
Compare
02520ac
to
5fb6ab4
Compare
This comment was marked as outdated.
This comment was marked as outdated.
So exciting 😁 |
5fb6ab4
to
9c6c9db
Compare
9c6c9db
to
b4be598
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi David, I leave a few more suggestions.
app/jobs/order_cycle_opened_job.rb
Outdated
def perform | ||
ActiveRecord::Base.transaction do | ||
recently_opened_order_cycles.find_each do |order_cycle| | ||
SubscriptionPlacementJob.perform_later(order_cycle.id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand but I think this field name is very misleading. What is a notification? I'd use the same terminology everywhere so that code readers will know what this means. It's a webhook. It could be called opened_webhook_sent_at.
I still think it would be much better and very easy to implement this outside the OC table (there's already lots of complexity in OCs).
A table where webhooks calls are registered is important imho. This would be one of them.
app/jobs/order_cycle_opened_job.rb
Outdated
ActiveRecord::Base.transaction do | ||
recently_opened_order_cycles.find_each do |order_cycle| | ||
SubscriptionPlacementJob.perform_later(order_cycle.id) | ||
OrderCycleWebhookJob.perform_later(order_cycle.id, 'order_cycle.opened') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type of event 'order_cycle.opened' should be extracted to constant somewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure where/how exactly yet, but will try to address this in my next update next week.
7a9993c
to
7c91867
Compare
Thanks @luisramos0 , this is valuable feedback. It seems this system design is not quite right, but I'm not clear on the best path and would like to communicate with the team. I will try to process this and perhaps create a forum post to discuss. |
This job is responsible for delivering a payload for one webhook event only. It allows the action to run asynchronously (and not slow down the calling process).
And thus retry later. I tried to test that it actually retries, or ensuring the job remained in the queue to be retried, but couldn't get it to work.
Best reviewed with whitespace hidden. Unfortunately the spec isn't allowed in CI. But it worked on my environment, I promise. I chose `xit` so that it doesn't run unnecessarily. Perhaps we could use `pending` instead, which would execute, and notify us if it suddenly started working one day. But I doubt it.
This will store the URL for each user that wants a notification. We probably don't need URL validation (it's not done on Enterprise for example). It could be validated by browser input, and anyway will be validated if the webhook actually works or not. Inspired by Keygen: https://keygen.sh/blog/how-to-build-a-webhook-system-in-rails-using-sidekiq/
Although we won't be allowing multiple in the this PR, we certainly plan to in the future. The migration helper add_reference couldn't handle the custom column name, so I had to put it together manually.
But not supplier owners.
Using the clever concurrency testing borrowed from SubscriptionPlacementJob, but I thought a shorter pause time (just 100ms) would be sufficient. I considered doing this with a new 'state' field (upcoming/open/close), but decided to keep it simple.
Allowing creation and deleting via the user association. It probably won't be much effort to allow editing and multiple records, but I cut it down to the minimum needed to avoid any further delays. I couldn't find a way to test a failure in the destroy method, but decided to keep the condition because I thought it was worth having.
Co-authored-by: Maikel <[email protected]>
9657974
to
9d5ca22
Compare
Sorry, that was a mistake in what I said. I just meant that I rebased from master to resolve conflicts. |
Now ready for a second review. @luisramos0 I understand if you don't have time, but if you do, would you like to re-review? |
Not sure if Luis will have time to re-review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good 👏 I left a few comments but nothing that should block the PR.
I like your use of pending
specs for work to be done, which I should start using myself
The best way to check if something changed or not, is with 'change' of course.
0c6bf20
to
d59074d
Compare
Ready to test! Tested deployment on au-staging: https://semaphoreci.com/openfoodfoundation/openfoodnetwork-2/servers/au-staging |
Hey @dacook, Thanks, really, for all the detail in the description of the PR. It comes really handy.
After enabling the API key for a user which is coordinator and distributor of an OC, we can check the UI: Creating the webhook displays: Proceeded to create an order cycle to open in a future time, taking note of the order cycle id ( Some moments afterwards, the webhook site was updated 🚀 we can check the payload below: And compare it to the info we get from the rails console, on the server:
All good here!
After deleting the endpoint, we receive no payload, when the OC is re-opened.
Also works, payload is delivered with the respective data ( Notice the different coordinator_name, when comparing to the examples in 1) and 3). The UI is according to specification, functionality as well, data is consistent. |
📚 Draft documentation |
What? Why?
A webhook can be sent to a service like Zapier, which can generate an email notification or perform some other action.
How?
Currently, we have no way of sending a webhook. There's no pre-built way of doing this for Rails surprisingly, although this tutorial looks like a good formula. I followed this, but found some parts unnecessary, and ended up with a different solution.
My focus was on making this work firstly for this single event type. Of course we'd like to extend it to for other events in the future, but let's get one working first before making further decisions.
There's bit going on here. A quick summary:
What should we test?
This is a new feature which doesn't touch any existing functionality.
It adds a new form to the Developer tab on the user Accounts page.
Suggested tests:
(refer back to the open tab for https://webhook.site/)
Release notes
Changelog Category: User facing changes
If documentation has been written, share a link in the release notes
Documentation updates
I think it should be documented wherever the API is documented. But I can't find any public mention of the API??!
It seems worth mentioning in the user guide: https://guide.openfoodnetwork.org/
Ideally, a test mode will display the webhook payload format, and act as in-place documentation. But this will come in a future PR.
In the future, I suggest we integrate with our OpenAPI documentation too.